package firbi.base;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import firbi.base.com.Bus;
import firbi.base.com.BusStop;
import firbi.base.util.Constants;
import android.app.Dialog;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.ContextMenu;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TabHost;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.TabHost.TabSpec;
/**
 * This class shows all up coming arrivals at a particular bus stop.
 * The stop id will be passed in a bundle form of an intent fired by
 * other activity 
 * @author samrach
 *
 */
public class StopView extends ListActivity {
	//ArrayList is used to parse data before putting it into SimpleAdapter to create a list
	//with 3 columns
	ArrayList<HashMap<String,String>> mList = new ArrayList<HashMap<String,String>>();
	private BusStop mBusStop; 						// used to store a current bus stop.
	private Bus[] mCurrentArrivals; 				// used to store list of current up-coming arrivals
	private ProgressDialog mProgressD; 				// used to access to dialog.
	public static  boolean mDISPLAYDIALOG=true;		// used to suppress the dialog when testing.
	private FavsDbAdapter mFavDbHelper;				//database adapter add stop id and route id to favorites
	
	private static final int sADD_FAV = 0; 			// id for add to favorites context menu.
	private static final int sCANCEL= sADD_FAV+1;	// id for cancel context menu.
	private boolean mThreadIsReady=false;			// a flag used to check readiness of a thread.
	private Dialog d;
	
    /*
     * (non-Javadoc)
     * @see android.app.Activity#onCreate(android.os.Bundle)
     */
    @Override
    public void onCreate(Bundle bundle){
    	super.onCreate(bundle);
       	setContentView(R.layout.stopview);
       	Bundle extras = getIntent().getExtras();
   		handleBundle(extras);
   		mFavDbHelper = new FavsDbAdapter(this);
        mFavDbHelper.open();
        registerForContextMenu(getListView());
        setUpHelpDialog();
    }
    
   /*
    * (non-Javadoc)
    * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
    */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    	MenuInflater inflater = getMenuInflater();
    	//initialized the menu for stop view currently we have view favorites
    	//search in map mode, search in list mode, and help.The menu can be 
    	//modified in res\menu\stop_view.xml
        inflater.inflate(R.menu.stopview_menu, menu);
        return true;
    }
    
    /*
     * (non-Javadoc)
     * @see android.app.Activity#onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo)
     */
    @Override
	public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
		super.onCreateContextMenu(menu, v, menuInfo);
		//initialized the context menu. Currently, have "Add to favorites" and "Cancel" 
        menu.add(0, sADD_FAV, 0, "Add to favorites");
        menu.add(0, sCANCEL, 0, "Cancel");
	}
    
    /*
     * (non-Javadoc)
     * @see android.app.Activity#onContextItemSelected(android.view.MenuItem)
     */
    @Override
	public boolean onContextItemSelected(MenuItem item) {
    	//handle the event triggered by the long click on the list item of our listview
		switch(item.getItemId()) {
			case sADD_FAV://add the destination, route number, route id, and stop id to the favorite database
				AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
				Bus tempBus = mCurrentArrivals[info.position];
				String destination, routeNumber, routeId, stopId, messageFromAdapter;
				destination = tempBus.getDestination();
				routeNumber = tempBus.getRoute().getRouteNumber();
				routeId = tempBus.getRoute().getId();
				stopId = mBusStop.getId();
				messageFromAdapter = translateReturnResultFromFavsDbAdapter(mFavDbHelper.createFav(destination, routeNumber, routeId, stopId));
				displayMessage(messageFromAdapter);
				return true;
			case sCANCEL://cancel the context menu
				return true;
		}
		return super.onContextItemSelected(item);
	}
    /**
     * Helper function used to associate return result from createFav of FavDbAdapter
     * @param returnFromAdapter constant defined in the FavDbAdapter
     * @return a message telling post condition of createFav 
     */
    private String translateReturnResultFromFavsDbAdapter(long returnFromAdapter){
    	if (returnFromAdapter >=0){
    		return Constants.sADDED_FAVORITE;
    	}else if(returnFromAdapter==FavsDbAdapter.FAV_ALREADY_IN_DB){
    		return Constants.sALREADY_FAVORITE;
    	}else if(returnFromAdapter==FavsDbAdapter.INTERNAL_ERROR){
    		return Constants.sPROBLEMS_WITH_DATABASE;
    	}else{
    		return "";
    	}
    }
    
    /**
     * Handle the extras passed by the other activity
     * @param extras extras is expected to contain route's id or both route's id and stop's id
     * 			if extras is null no information can be displayed.
     * 			if extras contains only stop's id, all up coming arrivals at the stop will be displayed.
     * 			if extras contains both value for both stop's id and route's id, all up coming arrivals 
     * 			at the stop will be displays and arrivals with route number matching route number associated
     * 			with route's id will be displayed first at the top of the list. 
     */
    private void handleBundle(Bundle extras){
    	String busStopId=null; 
    	String busRouteId=null;
    	if (extras != null) {// check for required extra
    		if (extras.containsKey(Constants.sSTOP_KEY)) busStopId = extras.getString(Constants.sSTOP_KEY);
    		if (extras.containsKey(Constants.sROUTE_KEY)) busRouteId = extras.getString(Constants.sROUTE_KEY);
    		if (busStopId!=null){
    			findThread(busStopId, busRouteId);
    		}
   		}
    }
    /**
     * Helper method used to display pop up message
     * @param msg message to be displayed
     */
    private void displayMessage(String msg) {
    	Toast t = Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT);
    	t.setGravity(Gravity.TOP, 0, 70);
    	if(msg.length() > 20) {
    		t.setDuration(Toast.LENGTH_LONG);
    	} else {
    		t.setDuration(Toast.LENGTH_SHORT);
    	}
    	t.show();
    }
    
    /**
     *Fill the header of part of the view. Nothing will be filled if busStop is null  
     */
    private void fillBusStopHeader(){
    	if (mBusStop==null) return;
    	String busStopAddress = mBusStop.getAddress();
    	String busStopNumber = "Stop# " + mBusStop.getStopNumber();
    	TextView addressLabel = (TextView) findViewById(R.id.address);
    	TextView stopNumberLabel = (TextView) findViewById(R.id.stopId);
    	addressLabel.setText(busStopAddress);
    	stopNumberLabel.setText(busStopNumber);
    }
 
    /**
     * Covert an arrivals array into list items of the listview 
     */
    private void generateArrivalList(Bus[] arrivals){
    	mList.clear();
    	for(int i=0;arrivals!=null && i < arrivals.length;i++){
    		Bus tempBus = arrivals[i];
    		addItem(tempBus.getRoute().getRouteNumber(), tempBus.getDestination(), predictedTimeandStatus(tempBus) , calculateArrivalStatus(tempBus));
    	}
    	refreshArriavlsList();
    }
    
    
    /**
     * This method is used to insert a row to the routes info list
     * @param routeColumn					the first column in the row
     * @param destinationColumnFirstRow		the second column first row
     * @param destinationColumnSecondRow	the second column second row
     * @param minutesColumn					the third column in the row
     */
	private void addItem(String routeColumn,String destinationColumn, String destinationColumnSecondRow, String minutesColumn) {
	  HashMap<String,String> item = new HashMap<String,String>();
	  item.put( "routeColumn", routeColumn );
	  item.put( "destinationColumnFirstRow", destinationColumn );
	  item.put( "destinationColumnSecondRow", destinationColumnSecondRow);
	  item.put( "minutesColumn", minutesColumn );
	  mList.add( item );
      
	}
	
	/**
	 * Take arrayList mList and use it create list item of our list view
	 */
	private void refreshArriavlsList(){
		SimpleAdapter notes = new SimpleAdapter( 
				this, 
				mList,
				R.layout.stopview_item_three_column_in_one_row,
				new String[] { "routeColumn","destinationColumnFirstRow","destinationColumnSecondRow","minutesColumn" },
				new int[] { R.id.col1, R.id.col2, R.id.col2_1, R.id.col3 }  );
       setListAdapter( notes );
	}
    
    
    /**
     * Helper method that return an array of arrivals such that elements.getId matches argument
     * routeId are place at the beginning of the array.
     * 
     * @param 	routeId the route's id that should be shown at the top of the list
     * @return 	arrivals with some elements placed at the beginning of it if element.getId()=routeId
     * 			arrivals if routeId is null
     */
    private Bus[] putGivenRouteFirst(Bus[] arrivals,String routeId){
    	if (routeId==null || arrivals==null) return arrivals;
    	Bus[] tempResult = arrivals.clone();
    	Bus[] upComingArrival = arrivals.clone();
    	int counter=0;
    	for(int i=0;i<upComingArrival.length;i++){//put given bus_route_id first
    		Bus tempBus = upComingArrival[i];
    		if (tempBus.getRoute().getId().equals(routeId)){
    			tempResult[counter++] = upComingArrival[i];
    			upComingArrival[i]=null;//set this element to null so that we know it was moved to top of the list
    		}
    	}
    	for(int i=0;i<upComingArrival.length;i++){//put the rest of array after the given_route_id
    		Bus tempBus = upComingArrival[i];
    		if (tempBus!=null){
    			tempResult[counter++] = tempBus;
    		}
    	}
    	return tempResult;
    }
   
    
    /**
     * Calculate the difference between predicted time and current time and return the arrival status of a bus
     * @param 	bus
     * @return 	string of how long before the bus arrive 					if difference > 0
     * 			"Now" i.e. bus can arrive any time now 						if difference = 0
     * 			string of negative number i.e the bus had left the stop		if difference < 0 
     */
    public String calculateArrivalStatus(Bus bus){
    	GregorianCalendar predictedTime = bus.getPredictedTime();
    	GregorianCalendar scheduledTime = bus.getScheduledTime();
    	GregorianCalendar currentTime = (GregorianCalendar) Calendar.getInstance();
    	if (predictedTime==null){
    		if (scheduledTime==null) return "NA"; //sometime predictedTime is null. Don't really know why
    		else return ""+ timeDiff(scheduledTime,currentTime);
    	}
    	int minutes = timeDiff(predictedTime,currentTime);
    	if (minutes==0) return "Now";
    	return ""+minutes;
    }
    
    /**
     * Calculate the different between time1 and time2 in minutes
     * @param time1 
     * @param time2
     * @return 	0 	when time1 = time2
     * 			<0	when time1 before time2
     * 			>0 	when time1 after time2
     */
    public int timeDiff(GregorianCalendar time1, GregorianCalendar time2){
    	GregorianCalendar tempTime;
    	int days, minutes, hours,factor;
    	if (time1.before(time2)) {// time1 < time2 => switching between time1 and time2 for ease of calculation
    		tempTime = time1;
    		time1 = time2;
    		time2 = tempTime;
    		factor = -1;
    	}else{ //time1 > time2 => no need to switch since time1 > time2
    		factor = 1;
    	}
    	
    	int minute1 = time1.get(Calendar.MINUTE);
    	int minute2 = time2.get(Calendar.MINUTE);
    	int day1 = time1.get(Calendar.DAY_OF_MONTH);
    	int day2 = time2.get(Calendar.DAY_OF_MONTH);
    	int hour1 = time1.get(Calendar.HOUR_OF_DAY);
    	int hour2 = time2.get(Calendar.HOUR_OF_DAY);
    	if (time1.equals(time2)){
    		return 0;
    	}else{
    		if (minute1 < minute2){ // minute1 < minute2 => add 60 minutes to minute1 and carry 1 to hour2
    			minutes = (minute1 + 60) - minute2;
    			hour2++;
    		}else minutes = minute1 - minute2; // minute1 > minute2, we don't need carry.
    		if (hour1 < hour2){ //hour1 < hour2 => add 24 hours to hour1 and carry 1 to day2
    			hours = (hour1 + 24) - hour2;
    			day2++;
    		}else hours = hour1 - hour2; // hour1 > hour2, we don't need to carry.
    		days = day1 -day2;
    		return factor * (days * 24 * 60 + hours * 60 + minutes);
    	}
    }
    
    /**
     * get predicted time and append status(ie delay, early, ontime) to it
     * @param bus
     * @return predicted time and status
     */
    public String predictedTimeandStatus(Bus bus){
    	GregorianCalendar predictedTime = bus.getPredictedTime();
    	GregorianCalendar scheduledTime = bus.getScheduledTime();
    	SimpleDateFormat format = new SimpleDateFormat("hh:mm");
    	String status;
    	if (predictedTime==null) {
    		if (scheduledTime!=null) 
    			// return only scheduledTime and no prediction of status because predictedTime is null
    			return format.format(bus.getScheduledTime().getTime()) + " - scheduled time.";
    		else 
    			// both predictedTime and scheduleTime are null, no useful info can be return 
    			return ""; 
    	}else{
    		if (scheduledTime==null){
    			status = ""; // can not return status because schedule is null
    		}else{
    			int minutes = timeDiff(predictedTime,scheduledTime);
            	if (minutes >0) { // delay
            		status = minutes + " min delay";
            	}else if (minutes==0){ // on time
            		status = "on time";
            	}else{ // early
            		status = Math.abs(minutes) + " min early";
            	}
    		}
    	}
    	return format.format(predictedTime.getTime()) + " - " + status;
    }
    

	/*
	 * (non-Javadoc)
	 * @see android.app.Activity#onStop()
	 */
	@Override
	public void onStop() {
		//handle the close event triggered when the view is close
		mFavDbHelper.close(); //close database adapter to prevent memory leak 
		super.onStop();
	}
	
	/*
	 * (non-Javadoc)
	 * @see android.app.ListActivity#onListItemClick(android.widget.ListView, android.view.View, int, long)
	 */
	@Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
		//handle the click event triggered when there is a short click on a listitem of our listview
        super.onListItemClick(l, v, position, id);
        Intent i = new Intent(this, RouteView.class);
        i.putExtra(Constants.sDEST_KEY, mCurrentArrivals[position].getDestination());
        i.putExtra(Constants.sROUTE_KEY, mCurrentArrivals[position].getRoute().getId());
		startActivity(i);//start routeview with destination and route id passed to it. 
	}	
	
	
	/*
	 * (non-Javadoc)
	 * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
	 */
    public boolean onOptionsItemSelected(MenuItem item) {
    	//handle the click event triggered when there is a click on our menu
    	Intent i;
	    switch (item.getItemId()) {
			case R.id.menu_item_mapsearch://open search in map mode view
				i = new Intent(this, Search.class);
				i.putExtra("isMapView", true);
				startActivity(i);
				return true;
			case R.id.menu_item_listsearch://open search in list mode view
				i = new Intent(this, Search.class);
				i.putExtra("isMapView", false);
				startActivity(i);
				return true;
			case R.id.menu_item_favs: //open go to favorites view
				i = new Intent(this, Favorites.class);
				startActivity(i);
				return true;
			case R.id.menu_item_help://open help dialog
		    	d.show();
				return true;
		}
	    return false;
    }
    
    /**
     * Setup help dialog
     */
    private void setUpHelpDialog(){
    	LayoutInflater li = (LayoutInflater)getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    	View v = li.inflate(R.layout.stopview_help_dialog, null);
        TabHost helpTab = (TabHost)v.findViewById(R.id.tabhost);
        helpTab.setup();
        TabSpec ts = helpTab.newTabSpec("TAB_TAG_1"); 
        ts.setIndicator("Main");
        ts.setContent(R.id.stopview_help_tab_main);
        helpTab.addTab(ts);
        TabSpec ts2 = helpTab.newTabSpec("TAB_TAG_2"); 
        ts2.setIndicator("Menu");
        ts2.setContent(R.id.stopview_help_tab_menu);
        helpTab.addTab(ts2);
        
        d = new Dialog(this);
        Button b = (Button)v.findViewById(R.id.btnExit);
        b.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				d.dismiss();
			}
        });
    	d.setTitle("Help");
    	d.setContentView(v);
    	d.setCanceledOnTouchOutside(true);
    }
    
    /**
     * used to suppress the dialog box when testing
     */
    public static void suppressDialogBox(){
    	mDISPLAYDIALOG = false;
    }
    
    /**
     * A clone copy of current arrivals
     * @return
     */
    public Bus[] getCurrentArrivals(){
    	return mCurrentArrivals!=null?mCurrentArrivals.clone():null;
    }
    
    
    /**
     * For check if thread call is finished. 
     */
    public boolean isThreadReady(){
    	return mThreadIsReady;
    }
	
	/** 
	 * Creates a separate thread to execute BusStop.find
	 */
	private void findThread(final String bus_stop_id,final String bus_route_id) {
		mThreadIsReady=false;
		if (mDISPLAYDIALOG)
			mProgressD = ProgressDialog.show(this, "", "Loading. Please wait...", true, false);
		Thread find = new Thread() {
			public void run() {
				mBusStop = BusStop.find(bus_stop_id);
				if (mBusStop!=null){
    				if (bus_route_id!=null){//arrange order of list items
                		mCurrentArrivals = putGivenRouteFirst(mBusStop.getUpcommingArrivals(), bus_route_id);
        			}else{//no need to sort because bus_route_id was not given
        				mCurrentArrivals = mBusStop.getUpcommingArrivals();
        			}
    			}
				findHandler.sendEmptyMessage(0);
			}
		};
		find.start();
	}
	
	/** Populates the UI with the new bus stops from BusStop.find
	 */
	private Handler findHandler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			fillBusStopHeader();
			generateArrivalList(mCurrentArrivals);
			if (mDISPLAYDIALOG)
				mProgressD.dismiss();
			mThreadIsReady=true;
		}
	};

}
